package ar.edu.itba.pod.legajo48240.model;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;
import org.joda.time.Duration;

import ar.edu.itba.balance.api.AgentsBalancer;
import ar.edu.itba.balance.api.AgentsTransfer;
import ar.edu.itba.balance.api.NodeAgent;
import ar.edu.itba.balance.api.NotCoordinatorException;
import ar.edu.itba.event.RemoteEventDispatcher;
import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;
import ar.edu.itba.node.api.StatisticReports;
import ar.edu.itba.pod.agent.runner.Agent;
import ar.edu.itba.pod.agent.runner.Simulation;
import ar.edu.itba.pod.legajo48240.balance.Balancer;
import ar.edu.itba.pod.legajo48240.event.DistributedEventDispatcher;
import ar.edu.itba.pod.legajo48240.node.ClusterManager;
import ar.edu.itba.pod.legajo48240.node.Statistics;
import ar.edu.itba.pod.legajo48240.simulation.DistributedSimulationManager;
import ar.edu.itba.pod.legajo48240.utils.Net;
import ar.edu.itba.pod.legajo48240.utils.Utils;
import ar.edu.itba.pod.time.TimeMappers;

public class Host{

	
	private NodeInformation node;

	private Simulation localSimulation;
	
	private ClusterAdministration network;

	private RemoteEventDispatcher dispatcher;
	
	private AgentsBalancer balancer;
	
	private AgentsTransfer transferer;
	
	private CountDownLatch finishedElection = new CountDownLatch(1);
	
	/**
	 *	when start coordinator is null 
	 */
	private NodeInformation coordinator;

	private Statistics statistics;

	

	private final static Logger LOGGER = Logger.getLogger(Host.class);
	
	public Host(String host, int port, String id) {

		Registry registry = null;

		try {
			node = new NodeInformation(host, port, id);

			localSimulation = new DistributedSimulationManager(
					TimeMappers.oneSecondEach(Duration.standardHours(8)), this);

			registry = LocateRegistry.createRegistry(port);
			LOGGER.info("REGISTRY started at port " + port + " in " + id());

			this.network=new ClusterManager(this);
			registry.bind(Node.CLUSTER_COMUNICATION, network());

			dispatcher = new DistributedEventDispatcher(this);
			registry.bind(Node.DISTRIBUTED_EVENT_DISPATCHER, dispatcher());
			
			balancer=new Balancer(this);
			registry.bind(Node.AGENTS_BALANCER, balancer());
			
			transferer=new Transferer(this);
			registry.bind(Node.AGENTS_TRANSFER, transferer());
			
			statistics=new Statistics(this);
			registry.bind(Node.STATISTIC_REPORTS, statistics());
			

		} catch (Exception e) {
			LOGGER.error("REGISTRY initialization problem");
		}
	}

	public StatisticReports statistics() {
		return statistics;
	}

	public Host(NodeInformation node) {
		this.node = node;
	}

	public ClusterAdministration network() {
		return network;
	}

	public Simulation simulation() {
		return localSimulation;
	}

	public String host() {
		return node.host();
	}

	public int port() {
		return node.port();
	}

	public String id() {
		return node.id();
	}

	public NodeInformation node() {
		return node;
	}
	
	public void finish(){
		if(dispatcher!=null){
			((DistributedEventDispatcher)dispatcher).finish();
		}
	}

	public AgentsBalancer balancer() {
		return balancer;
	}
	
	
	public  void setCoordinator(NodeInformation node) {
		if(node!=null){
			LOGGER.info("SETEO EL COORDINADOR " + node.id());
			this.coordinator=node;
			finishedElection.countDown();
		}else{
			finishedElection=new CountDownLatch(1);
			LOGGER.info(this.node.id() +" in election ... ");
		}
		
	}
	
	public NodeInformation getCoordinator() {
		
		if(coordinator==null){
			LOGGER.info("NO TENGO COORDINADOR");
			// maybe on election or without coordinator so start election
			
			try {
				Net.getBalancer(node()).bullyElection(node(), System.nanoTime());
			} catch (RemoteException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (NotBoundException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		try {
			LOGGER.debug("AWAIT");
			finishedElection.await();
			//			LOGGER.info("TERMINO LA ELECCION");
		} catch (InterruptedException e) { 
			e.printStackTrace(); 
		}
		return coordinator;
	}
	
	public RemoteEventDispatcher dispatcher() {
		return dispatcher;
	}

	public AgentsTransfer transferer() {
		return transferer;
	}
	
	public void shutdown(){
		
//		// if coordinator set null
//		
//		if(this.node().equals(this.getCoordinator())){
//			this.coordinator=null;
//		}
		
		// disconnect from others
		
		try {
			for( NodeInformation node: this.network().connectedNodes()){
				if(!node.equals(this.node())){ // other than me
					try {
						LOGGER.info("disconnecting " + node.id() + " from: " + node().id());
						Net.getCluster(node.host(), node.port()).disconnectFromGroup(this.node());
					} catch (NotBoundException ignore) {} // already disconnected
				}
			}
		} catch (RemoteException e1) {	} // local method
		
		
		// exiles my agents
		
		boolean coordinatorNotMatch=false;
		
		while( !coordinatorNotMatch ){
			try {
				NodeInformation coordinator=this.getCoordinator();
				List<Agent> myAgents=Utils.clone(this.simulation().getAgentsRunning());
				List<NodeAgent> myNodeAgents= new ArrayList<NodeAgent>();
				for(Agent a: myAgents){
					myNodeAgents.add(new NodeAgent(this.node(),a));
				}
				Net.getBalancer(coordinator).shutdown(myNodeAgents);
				coordinatorNotMatch=true;
				
			} catch (NotCoordinatorException e) {
				LOGGER.info("NOT MATCH COORDINATOR... shoud be:" + e.getNewCoordinator());
				this.setCoordinator(e.getNewCoordinator());
			} catch (Exception e) {
				LOGGER.info("EXCEPTION:");
				Net.disconnectFromGroup(this, this.getCoordinator());
				e.printStackTrace();
			}
		}
		
	}



}
